<template>

  <!-- 注意，使用 autoResize 配置时，需要在画布容器外面再套一层宽高都是 100% 的外层容器，在外层容器上监听尺寸改变，当外层容器大小改变时，画布自动重新计算宽高以及元素位置 -->
  <div style="width:100%; height:100%; background-color: white">


    <el-row type="flex" align="middle" style="margin-top: 10px; height: 50px;">
      <!-- 左侧固定150px -->
      <div style="width: 150px; display: flex; justify-content: center; align-items: center;">
        <h3 style="margin: 0;">网络拓扑图</h3>
      </div>

      <!-- 右侧自适应区域 -->
      <div style="flex: 1; height: 100%; display: flex; align-items: center; margin-top: 10px">
        <div>
          <el-button size="mini" type="primary" plain @click="zoomAmplify()">放大画布</el-button>
          <el-button size="mini" type="primary" plain @click="zoomReduce()">缩小画布</el-button>
          <el-button size="mini" type="primary" plain @click="zoomCenterContent()">居中</el-button>
          <el-button size="mini" type="primary" plain @click="exportImage()">导出图片</el-button>
          <el-button size="mini" type="warning" @click="toSaveUpd()">保存/更新</el-button>
        </div>
      </div>
    </el-row>



    <el-row type="flex" style="background-color: white">
      <el-col :span="19" >
        <div id="container">
          <!--图形工具栏-->
          <div ref="stencilContainer" id="stencil" ></div>
          <div ref="graphContainer" id="graph-container"></div>
          <div ref="refMiniMapContainer" />
        </div>
      </el-col>

      <el-col :span="5" >

        <div style="margin: 10px">
          <el-alert title="复制：ctrl + c" type="success" :closable="false"></el-alert>
          <el-alert title="粘贴：ctrl + v" type="success" :closable="false"></el-alert>
          <el-alert title="剪切：ctrl + x" type="success" :closable="false"></el-alert>
          <el-alert title="删除：delete" type="warning" :closable="false"></el-alert>
          <el-alert title="撤销：ctrl + z" type="success" :closable="false"></el-alert>
          <el-alert title="反撤销：ctrl + shift + z" type="success" :closable="false"></el-alert>
          <el-alert title="获取节点属性：双击节点" type="success" :closable="false"></el-alert>
          <el-alert title="节点大小调整：单击节点" type="success" :closable="false"></el-alert>
          <el-alert title="移动画布：ctrl + 鼠标左键" type="success" :closable="false"></el-alert>
          <el-alert title="放大缩小：ctrl + 鼠标滚动" type="success" :closable="false"></el-alert>
        </div>

        <!--节点-->
        <div style="margin-top: 20px; padding-right: 5px" v-if="currentCell != null && currentCell.isNode()">
          <h3>属性信息</h3>

          <el-row>
            <el-col :span="7">
              <span>文字：</span>
            </el-col>
            <el-col :span="17">
              <el-input size="mini" v-model="currentSelectNode.attrs.text.text" clearable type="textarea" :autosize="{minRows: 2}" />
            </el-col>
          </el-row>

          <el-row style="margin-top: 5px">
            <el-col :span="7">
              <span>填充色：</span>
            </el-col>
            <el-col :span="17">
              <el-row>
                <el-col :span="5"><el-color-picker size="mini" v-model="currentSelectNode.attrs.body.fill"></el-color-picker></el-col>
                <el-col :span="19"><el-input size="mini" v-model="currentSelectNode.attrs.body.fill"  /></el-col>
              </el-row>
            </el-col>
          </el-row>

          <el-row style="margin-top: 5px">
            <el-col :span="7">
              <span>边框颜色：</span>
            </el-col>
            <el-col :span="17">
              <el-row>
                <el-col :span="5"><el-color-picker size="mini" v-model="currentSelectNode.attrs.body.stroke"></el-color-picker></el-col>
                <el-col :span="19"><el-input size="mini" v-model="currentSelectNode.attrs.body.stroke"  /></el-col>
              </el-row>
            </el-col>
          </el-row>

          <el-row style="margin-top: 5px">
            <el-col :span="7">
              <span>文字颜色：</span>
            </el-col>
            <el-col :span="17">
              <el-row>
                <el-col :span="5"><el-color-picker size="mini" v-model="currentSelectNode.attrs.text.fill"></el-color-picker></el-col>
                <el-col :span="19"><el-input size="mini" v-model="currentSelectNode.attrs.text.fill"  /></el-col>
              </el-row>
            </el-col>
          </el-row>

          <el-row style="margin-top: 5px">
            <el-col :span="7">
              <span>文字大小：</span>
            </el-col>
            <el-col :span="17"><el-input-number size="mini" v-model="currentSelectNode.attrs.text.fontSize"  /></el-col>
          </el-row>

          <el-divider></el-divider>

          <el-button size="mini" type="primary" @click="updNodeInfo" >更新节点</el-button>


        </div>


        <!--连接线-->
        <div style="margin-top: 20px; padding-right: 5px" v-if="currentCell != null && currentCell.isEdge()">
          <h3>属性信息</h3>

          <el-row>
            <el-col :span="7">
              <span>文字：</span>
            </el-col>
            <el-col :span="17">
              <el-input size="mini" v-model="currentSelectEdge.labels[0].attrs.label.text" clearable type="textarea" :autosize="{minRows: 2}" />
            </el-col>
          </el-row>

          <el-row style="margin-top: 5px">
            <el-col :span="7">
              <span>文字颜色：</span>
            </el-col>
            <el-col :span="17">
              <el-row>
                <el-col :span="5"><el-color-picker size="mini" v-model="currentSelectEdge.labels[0].attrs.label.fill"></el-color-picker></el-col>
                <el-col :span="19"><el-input size="mini" v-model="currentSelectEdge.labels[0].attrs.label.fill"  /></el-col>
              </el-row>
            </el-col>
          </el-row>

          <el-row style="margin-top: 5px">
            <el-col :span="7">
              <span>文字大小：</span>
            </el-col>
            <el-col :span="17"><el-input-number size="mini" v-model="currentSelectEdge.labels[0].attrs.label.fontSize"  /></el-col>
          </el-row>

          <el-row style="margin-top: 5px">
            <el-col :span="7">
              <span>线颜色：</span>
            </el-col>
            <el-col :span="17">
              <el-row>
                <el-col :span="5"><el-color-picker size="mini" v-model="currentSelectEdge.attrs.line.stroke"></el-color-picker></el-col>
                <el-col :span="19"><el-input size="mini" v-model="currentSelectEdge.attrs.line.stroke"  /></el-col>
              </el-row>
            </el-col>
          </el-row>

          <el-row style="margin-top: 5px">
            <el-col :span="7">
              <span>线粗细：</span>
            </el-col>
            <el-col :span="17"><el-input-number size="mini" v-model="currentSelectEdge.attrs.line.strokeWidth"  /></el-col>
          </el-row>

          <el-row style="margin-top: 10px">
            <el-col :span="7">
              <span>启用虚线：</span>
            </el-col>
            <el-col :span="17">
              <el-radio v-model="currentSelectEdge.attrs.line.strokeDasharray" label="5">虚线</el-radio>
              <el-radio v-model="currentSelectEdge.attrs.line.strokeDasharray" label="2">实线</el-radio>
            </el-col>
          </el-row>



          <el-divider></el-divider>

          <el-button size="mini" type="primary" @click="updEdgeInfo" >更新边</el-button>

        </div>


      </el-col>
    </el-row>


  </div>

</template>

<script>

// 文档 https://x6.antv.antgroup.com/

import http from '@/utils/http'
import {clearInvalid, desEncrypt, checkNull} from "@/utils/utils";

import { Graph, Shape } from "@antv/x6";
import { Stencil } from "@antv/x6-plugin-stencil";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
// 对齐
import { Snapline } from "@antv/x6-plugin-snapline";
import { Export } from '@antv/x6-plugin-export'
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { MiniMap } from '@antv/x6-plugin-minimap'
import { History } from "@antv/x6-plugin-history";



export default {
  name: "NetEnvList",
  data() {
    return {
      // 数据库查询出的网络拓扑信息
      siteNetwork: {
        siteId: null,
        name: null,
        flowChart: null,
        id: null,
      },
      // 当前节点或边
      currentCell: null,
      // 当前选中的节点-深拷贝，防止相互印象
      currentSelectNode: {
        attrs: {
          body:{
            // 边框颜色，填充色
            fill: null,
            stroke: null,
          },
          text:{
            // 文字样式
            fontSize: null,
            fill: null,
            text: null,
          },
        },
      },
      // 边
      currentSelectEdge: {
        labels:[{
          attrs: {
            label:{
              // 显示的文字
              text: null,
              fill: null,
              fontSize: 1
            }
          },
        }],
        attrs:{
          line:{
            // 颜色
            stroke: null,
            // 粗细
            strokeWidth: 1,
            // 是否虚线
            strokeDasharray: 0,
          },
        }
      },
    }
  },
  mounted() {
    // 初始化画布
    this.initializeGraph();
    // 自定义节点注册
    this.registerCustomShapes();
    // 图形工具栏
    this.setupStencil();
    // 快捷键绑定
    this.setupShortcutsAndEvents();
    // 获取初始化数据
    this.getBySiteId()

    this.$store.commit('navigationListChange', [
      {name: '场所列表', url: '/toSiteList', query: {}},
      {name: '场所详情', url: '/toSiteLayout', query: {siteId: this.$route.query.siteId}},
      {name: '网络环境', url: '/toNetEnvList', query: {siteId: this.$route.query.siteId}},
    ])
  },
  beforeDestroy() {
    if (this.graph) {
      this.graph.dispose();
    }
  },
  methods: {
    /**
     * 初始化画布
     * @returns {Edge|boolean}
     */
    initializeGraph() {
      this.graph = new Graph({
        // 渲染的id
        container: this.$refs.graphContainer,
        // 自动布局大小
        autoResize: true,
        // true 默认网格样式，可以自定义
        grid: true,
        // grid: {
        //   size: 20,         // 网格大小（点之间的间距）
        //   visible: true,    // 是否显示网格
        //   type: 'mesh',// 网格类型：'dot'(点状), 'fixedDot'(固定大小点状), 'mesh'(网格线)
        //   args: [
        //     {color: '#aaa', thickness: 1},  // 主网格线样式
        //     {color: '#ddd', thickness: 1, factor: 4}  // 次网格线样式（当使用doubleMesh时）
        //   ]
        // },
        // 支持鼠标拖放
        panning: true,
        // 鼠标缩放配置
        mousewheel: {
          // 启用鼠标滚轮缩放功能
          enabled: true,
          // 以鼠标位置为中心进行缩放
          zoomAtMousePosition: true,
          // 需要配合修饰键(ctrl键)才能缩放
          modifiers: "ctrl",
          // 最小缩放比例(50%)
          minScale: 0.5,
          // 最大缩放比例(300%)
          maxScale: 3,
        },
        // 支持节点组合。
        embedding: {
          enabled: true,
        },
        // 节点之间连接线的行为和样式，规则
        connecting: {
          // manhattan 使用曼哈顿路由算法（直角连线）,"orth": 正交连线（类似直角但更灵活）,"metro": 地铁线路由（美观的曲线）,"normal": 直接直线连接
          router: "manhattan",
          connector: {
            // "rounded": 圆角连接,"jumpover": 交叉处有跳线效果,"smooth": 平滑曲线连接
            name: "rounded",
            args: {
              // 圆角半径为8像素
              radius: 8,
            },
          },
          // 锚点位于节点中心
          anchor: "center",
          // 连接点使用锚点位置
          connectionPoint: "anchor",
          // 不允许连接到空白画布区域
          allowBlank: false,
          // 是否允许创建循环连线，即边的起始节点和终止节点为同一节点，默认为 true
          allowLoop: false,
          // 拖动连线时高亮显示可用连接桩/节点:contentReference[oaicite:3]{index=3} ,即便没有触发 node:mouseenter 事件，也能达到可见效果
          highlight: true,
          snap: {
            // 20像素范围内自动吸附
            radius: 30,
          },
          /**
           * 创建连线时的默认样式
           */
          createEdge() {
            return new Shape.Edge({
              attrs: {
                line: {
                  // 连线颜色
                  stroke: "#1890ff",
                  // 连线宽度
                  strokeWidth: 1,
                  // 虚线,设置为 0 或 null 时，边为实线。当设置为其他值时，边会显示为虚线。例如，strokeDasharray: '5 5' 表示虚线的线段长度和间隔长度均为 5
                  strokeDasharray: 5,
                  // 虚线的样式，还要全局的样式才行，否则不会动
                  style: {
                    animation: 'ant-line 30s infinite linear',
                  },
                  // 目标端箭头样式
                  targetMarker: {
                    // block 块状箭头，还有可选，classic，diamond，cross，async，path，circle，circlePlus，ellipse 具体参考 https://x6.antv.antgroup.com/tutorial/basic/edge
                    name: "block",
                    // 箭头宽度
                    width: 12,
                    // 箭头高度
                    height: 8,
                    // 箭头颜色
                    fill: '#1890ff'
                  },
                },
              },
              // 添加文字标签，去掉这里的就没有数据
              labels: [{
                attrs: {
                  label: {
                    // 显示的文字
                    text: "",
                    fill: "#666",
                    fontSize: 12
                  }
                }
              }],
              // 图层级别， DOM 层级，值越大层级越高。
              zIndex: 0,
            });
          },
          // 连接验证函数
          validateConnection({targetMagnet}) {
            // 必须有连接桩
            return !!targetMagnet;
          },
        },
        // 当连接桩被吸附时的高亮效果
        highlighting: {
          // 连接桩可以被连接时在连接桩外围围渲染一个包围框
          magnetAvailable: {
            name: 'stroke',
            args: {
              attrs: {
                fill: '#fff',
                stroke: '#A4DEB1',
                strokeWidth: 4,
              },
            },
          },
          // 连接桩吸附连线时在连接桩外围围渲染一个包围框
          magnetAdsorbed: {
            name: 'stroke',
            args: {
              attrs: {
                fill: '#fff',
                stroke: '#A4DEB1',
                strokeWidth: 4,
              },
            },
          },
        },
      });
      /**
       * 配置插件，插件初始化
       */
      this.graph
        .use(
          // 变形插件
          new Transform({
            // 允许调整节点大小，默认false
            resizing: true,
            // 允许旋转节点，默认false
            rotating: true,
            minWidth: 20,
            minHeight: 20,
          })
        )
        .use(
          // 选择插件
          new Selection({
            // 启用框选功能
            rubberband: true,
            // 按住Shift才能框选
            modifiers: 'shift',
            // 显示节点选择框
            showNodeSelectionBox: true,
          })
        )
        // 对齐辅助线插件
        .use(new Snapline())
        // 键盘快捷键插件，默认支持 Delete/Backspace：删除选中元素  Ctrl+C / Cmd+C：复制 Ctrl+V / Cmd+V：粘贴 Ctrl+Z / Cmd+Z：撤销 Ctrl+Shift+Z / Cmd+Shift+Z：重做
        .use(new Keyboard())
        // 剪贴板插件
        .use(new Clipboard())
        .use(new Export())
        // .use(new MiniMap(
        //   {
        //     container: this.$refs.refMiniMapContainer,
        //     width: 200,
        //     height: 160,
        //     padding: 10,
        //   }
        // ))
        // 操作历史插件
        .use(new History());
    },
    /**
     * 自定义节点注册
     */
    registerCustomShapes() {
      // 连接桩配置
      const ports = {
        groups: {
          // 顶部连接桩
          top: {
            position: "top",
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden",
                },
              },
            },
          },
          // 左上
          topLeft: {
            position: {
              name: 'absolute',
              args: {x: 0, y: 0}
            },
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden"
                }
              }
            }
          },
          // 右上
          topRight: {
            position: {
              name: 'absolute',
              args: {x: '100%', y: 0}
            },
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden"
                }
              }
            }
          },
          // 右侧连接桩
          right: {
            position: "right",
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden",
                },
              },
            },
          },
          // 底部连接桩
          bottom: {
            position: "bottom",
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden",
                },
              },
            },
          },
          // 左下连接桩
          bottomLeft: {
            position: {
              name: "absolute",
              // 左下角
              args: {x: 0, y: "100%"}
            },
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden",
                },
              },
            },
          },
          // 右下连接桩
          bottomRight: {
            position: {
              name: "absolute",
              args: {x: "100%", y: "100%"}
            },
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden",
                },
              },
            },
          },
          // 左侧连接桩
          left: {
            position: "left",
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: "#5F95FF",
                strokeWidth: 1,
                fill: "#fff",
                style: {
                  visibility: "hidden",
                },
              },
            },
          },
        },
        // 默认在所有方向都放置连接桩
        items: [
          {group: "top",},
          {group: "topLeft",},
          {group: "topRight",},
          {group: "right",},
          {group: "bottom",},
          // 底部两组
          {group: "bottomLeft"},
          {group: "bottomRight"},
          {group: "left",},
        ],
      };

      Graph.registerNode(
        /* 带圆角的矩形节点
        Stencil 中的节点是通过 shape 属性指定其类型，而这个类型必须是之前通过 Graph.registerNode 注册过的
        "custom-circle"、"custom-image"）是用户自定义的，用于标识不同类型的节点。它们必须是唯一的字符串，用于在图表中区分不同的节点类型
        */
        "custom-rect",
        {
          // 继承基础矩形, 可选 rect 矩形  circle 圆形 ellipse 椭圆 polygon 多边 polyline 折线 path 路径 image 图片 html HTML 节点
          inherit: "rect",
          width: 66,
          height: 36,
          attrs: {
            // 矩形主体样式
            body: {
              strokeWidth: 1,
              // 蓝色边框
              stroke: "#5F95FF",
              // 浅蓝色填充
              fill: "#EFF4FF",
            },
            text: {
              // 文字样式
              fontSize: 12,
              fill: "#262626",
            },
          },
          // 使用标准连接桩配置
          ports: {
            ...ports
          },
        },
        // 立即生效
        true
      );

      Graph.registerNode(
        // 自定义多边形节点
        "custom-polygon",
        {
          // 继承基础多边形节点
          inherit: "polygon",
          width: 66,
          height: 36,
          attrs: {
            body: {
              // 边框宽度
              strokeWidth: 1,
              // 边框颜色
              stroke: "#5F95FF",
              // 填充颜色
              fill: "#EFF4FF",
            },
            text: {
              // 文本字体大小
              fontSize: 12,
              fill: "#262626",
            },
          },
          ports: {
            ...ports,
            // 使用连接桩配置
            items: [
              {
                group: "top",
              },
              {
                group: "bottom",
              },
            ],
          },
        },
        true
      );

      Graph.registerNode(
        // 注册自定义圆形节点
        "custom-circle",
        {
          // 继承基础圆形节点
          inherit: "circle",
          width: 45,
          height: 45,
          attrs: {
            body: {
              strokeWidth: 1,
              stroke: "#5F95FF",
              fill: "#EFF4FF",
            },
            text: {
              fontSize: 12,
              fill: "#262626",
            },
          },
          ports: {...ports},
        },
        true
      );

      Graph.registerNode(
        // 文本节点
        "custom-textBlock",
        {
          // 继承基础圆形节点
          inherit: "rect",
          width: 66,
          height: 36,
          text: '',
          attrs: {
            label: {
              contenteditable: "true",// 编辑开启
            },
            body: {
              fill: '#fff',
              stroke: '#fff',
              rx: 4,
              ry: 4,
            },
          },
          // ports: {...ports}, 不给连接桩
        },
        true
      );

      Graph.registerNode(
        // 注册自定义图片节点
        "custom-image",
        {
          // 继承基础矩形节点
          inherit: "rect",
          width: 52,
          height: 52,
          // 可以类比为html attrs 类比 CSS
          markup: [
            {
              // 边框颜色
              tagName: "rect",
              selector: "body",
            },
            {
              // 图片元素
              tagName: "image",
            },
            {
              // 文本元素
              tagName: "text",
              selector: "label",
            },
          ],
          attrs: {
            // 定义节点的样式
            body: {
              // 边框颜色
              stroke: "#EFF4FF",
              // 填充颜色
              fill: "#fff",
            },
            image: {
              // 图片的宽度
              width: 26,
              // 图片的高度
              height: 26,
              // 图片相对于节点的水平偏移
              refX: 13,
              // 图片相对于节点的垂直偏移
              refY: 10,
            },
            label: {
              // 文本相对于节点的水平偏移
              refX: 25,
              // 文本相对于节点的垂直偏移
              refY: '100%',
              // 文本的水平对齐方式
              // textAnchor: "left",
              textAnchor: "middle",
              // 文本的垂直对齐方式
              textVerticalAnchor: "bottom",
              // 文本字体大小
              fontSize: 9,
              // 文本颜色
              // fill: "#fff",
            },
          },
          ports: {...ports},
        },
        true
      );
    },
    /**
     * 图形工具栏
     */
    setupStencil() {
      const stencil = new Stencil({
        // Stencil 的标题
        title: "全部节点",
        // 指定目标图表
        target: this.graph,
        stencilGraphWidth: 200,
        stencilGraphHeight: 180,
        // 是否可以折叠
        collapsable: true,
        // 查询框上面的问题
        placeholder: '请输入标签名',
        // 支持查询能力
        search: (cell, keyword, groupName, stencil) => {
          return cell.label.indexOf(keyword) !== -1;
        },
        // 定义 Stencil 的分组
        groups: [
          {
            // 分组标题
            title: "基础流程图",
            // 分组名称
            name: "group1",
            graphHeight: 230,
          },
          {
            title: "网络拓扑",
            name: "group2",
            // 该分组的 Stencil 高度
            graphHeight: 500,
            layoutOptions: {
              // 每行的高度
              rowHeight: 70,
            },
          },
          {
            title: "组件",
            name: "group3",
            // 该分组的 Stencil 高度
            graphHeight: 450,
            layoutOptions: {
              // 每行的高度
              rowHeight: 70,
            },
          },
        ],
        // Stencil 的布局选项
        layoutOptions: {
          // 每行显示的列数
          columns: 2,
          // 每列的宽度
          columnWidth: 80,
          // 每行的高度
          rowHeight: 55,
        },
      });

      // 绑定到html中
      this.$refs.stencilContainer.appendChild(stencil.container);

      // 初始化图形
      const r1 = this.graph.createNode({
        // 使用自定义矩形节点，Graph.registerNode 方法中定义的节点类型（如 "custom-rect"）需要与 Stencil 中加载的节点类型对应起来
        shape: "custom-rect",
        // 节点标签
        label: "开始",
        attrs: {
          body: {
            // 矩形的圆角半径
            rx: 20,
            ry: 26,
          },
        },
      });
      const r2 = this.graph.createNode({
        shape: "custom-rect",
        label: "过程",
      });
      const r3 = this.graph.createNode({
        shape: "custom-rect",
        attrs: {
          body: {
            // 矩形的圆角半径
            rx: 6,
            ry: 6,
          },
        },
        label: "可选过程",
      });
      const r4 = this.graph.createNode({
        shape: "custom-polygon",
        attrs: {
          body: {
            // 多边形的顶点坐标
            refPoints: "0,10 10,0 20,10 10,20",
          },
        },
        label: "决策",
      });
      const r5 = this.graph.createNode({
        shape: "custom-polygon",
        attrs: {
          body: {
            // 多边形的顶点坐标
            refPoints: "10,0 40,0 30,20 0,20",
          },
        },
        // 节点标签
        label: "数据",
      });
      const r6 = this.graph.createNode({
        shape: "custom-circle",
        label: "连接",
      });
      const r7 = this.graph.createNode({
        shape: "custom-textBlock",
        label: "文本",
      });

      // 将这些节点加载到 group1 分组中
      stencil.load([r1, r2, r3, r4, r5, r6, r7], "group1");

      const imageShapes = [
        {
          label: "IDS",
          image: require("@/assets/images/netEnv/IDS.svg"),
        },
        {
          label: "Server",
          image: require("@/assets/images/netEnv/Server.svg"),
        },
        {
          label: "WAF",
          image: require("@/assets/images/netEnv/WAF.svg"),
        },
        {
          label: "二层交换机",
          image: require("@/assets/images/netEnv/switch2.svg"),
        },
        {
          label: "三层交换机",
          image: require("@/assets/images/netEnv/switch3.svg"),
        },
        {
          label: "核心交换机",
          image: require("@/assets/images/netEnv/switch1.svg"),
        },
        {
          label: "服务器",
          image: require("@/assets/images/netEnv/server01.svg"),
        },
        {
          label: "网关",
          image: require("@/assets/images/netEnv/getway.svg"),
        },
        {
          label: "计算机",
          image: require("@/assets/images/netEnv/computer.svg"),
        },
        {
          label: "路由器",
          image: require("@/assets/images/netEnv/router1.svg"),
        },
        {
          label: "路由器",
          image: require("@/assets/images/netEnv/router2.svg"),
        },
        {
          label: "手机",
          image: require("@/assets/images/netEnv/phone.svg"),
        },
        {
          label: "防火墙",
          image: require("@/assets/images/netEnv/firewall.svg"),
        },
        {
          label: "虚拟机",
          image: require("@/assets/images/netEnv/vm.svg"),
        },
      ];
      // 循环创建图片节点
      const imageNodes = imageShapes.map((item) =>
        this.graph.createNode({
          // 使用自定义图片节点
          shape: "custom-image",
          label: item.label,
          attrs: {
            image: {
              // 图片的 URL
              "xlink:href": item.image,
            },
          },
        })
      );
      // 将这些节点加载到 group2 分组中
      stencil.load(imageNodes, "group2");


      // 组件
      const imageShapesComponent = [
        {
          label: "KafKa",
          image: require("@/assets/images/netEnv/Kafka.svg"),
        },
        {
          label: "MQ",
          image: require("@/assets/images/netEnv/MQ.svg"),
        },
        {
          label: "nginx",
          image: require("@/assets/images/netEnv/nginx.svg"),
        },
        {
          label: "redis",
          image: require("@/assets/images/netEnv/redis.svg"),
        },
        {
          label: "数据库",
          image: require("@/assets/images/netEnv/db.svg"),
        },
        {
          label: "zookeeper",
          image: require("@/assets/images/netEnv/zookeeper.svg"),
        },
        {
          label: "task",
          image: require("@/assets/images/netEnv/task.svg"),
        },
        {
          label: "全文检索",
          image: require("@/assets/images/netEnv/es.svg"),
        },
        {
          label: "查询",
          image: require("@/assets/images/netEnv/search.svg"),
        },
        {
          label: "用户",
          image: require("@/assets/images/netEnv/user.svg"),
        },
        {
          label: "网络",
          image: require("@/assets/images/netEnv/net.svg"),
        },
      ];
      // 循环创建图片节点
      const imageNodesComponent = imageShapesComponent.map((item) =>
        this.graph.createNode({
          // 使用自定义图片节点
          shape: "custom-image",
          label: item.label,
          attrs: {
            image: {
              // 图片的 URL
              "xlink:href": item.image,
            },
          },
        })
      );
      // 将这些节点加载到 group2 分组中
      stencil.load(imageNodesComponent, "group3");
    },
    /**
     * 快捷键绑定
     * 支持的快捷键，ctrl ， meta：Command 键, shift , alt ,可以与字母键（如 a、z 等）或功能键（如 backspace、enter 等）组合使用
     */
    setupShortcutsAndEvents() {
      // 复制
      this.graph.bindKey(["meta+c", "ctrl+c"], () => {
        // 获取当前选中的单元格
        const cells = this.graph.getSelectedCells();
        if (cells.length) {
          // 复制选中的单元格
          this.graph.copy(cells);
        }
        // 阻止默认行为
        return false;
      });
      // 剪切
      this.graph.bindKey(["meta+x", "ctrl+x"], () => {
        const cells = this.graph.getSelectedCells();
        if (cells.length) {
          // 剪切选中的单元格
          this.graph.cut(cells);
        }
        return false;
      });
      // 粘贴
      this.graph.bindKey(["meta+v", "ctrl+v"], () => {
        // 检查剪贴板是否为空
        if (!this.graph.isClipboardEmpty()) {
          // 粘贴单元格，并偏移 32 像素
          const cells = this.graph.paste({offset: 32});
          // 清空当前选中
          this.graph.cleanSelection();
          // 选中粘贴的单元格
          this.graph.select(cells);
        }
        return false;
      });
      // 撤销
      this.graph.bindKey(["meta+z", "ctrl+z"], () => {
        if (this.graph.canUndo()) {
          // 撤销上一步操作
          this.graph.undo();
        }
        return false;
      });
      // 重做
      this.graph.bindKey(["meta+shift+z", "ctrl+shift+z"], () => {
        // 检查是否可以重做
        if (this.graph.canRedo()) {
          // 重做上一步操作
          this.graph.redo();
        }
        return false;
      });
      // 全选
      this.graph.bindKey(["meta+a", "ctrl+a"], () => {
        // 获取所有节点
        const nodes = this.graph.getNodes();
        if (nodes) {
          // 选中所有节点
          this.graph.select(nodes);
        }
      });
      // 删除
      this.graph.bindKey("delete", () => {
        const cells = this.graph.getSelectedCells();
        if (cells.length) {
          // 删除选中的单元格
          this.graph.removeCells(cells);
        }
      });
      // 缩放-放大
      this.graph.bindKey(["ctrl+1", "meta+1"], () => {
        // 获取当前缩放比例
        const zoom = this.graph.zoom();
        if (zoom < 1.5) {
          // 放大 0.1
          this.graph.zoom(0.1);
        }
      });
      // 缩放-缩小
      this.graph.bindKey(["ctrl+2", "meta+2"], () => {
        const zoom = this.graph.zoom();
        if (zoom > 0.5) {
          this.graph.zoom(-0.1);
        }
      });
      // 控制连接桩显示/隐藏
      const showPorts = (ports, show) => {
        ports.forEach((port) => {
          // 根据参数显示或隐藏连接桩
          port.style.visibility = show ? "visible" : "hidden";
        });
      };
      // 节点上鼠标进入
      this.graph.on("node:mouseenter", ({node}) => {
        const view = this.graph.findViewByCell(node)
        if (view) {
          const ports = view.container.querySelectorAll('.x6-port-body');
          showPorts(ports, true);
        }
      });
      this.graph.on("node:mouseleave", ({node}) => {
        const view = this.graph.findViewByCell(node)
        if (view) {
          const ports = view.container.querySelectorAll('.x6-port-body');
          showPorts(ports, false);
        }
      });
      // 双击节点
      // this.graph.on("node:dblclick", ({e, cell, view}) => {
      // 单击节点
      this.graph.on("node:click", ({e, cell, view}) => {
        // 双击删除
        // view.cell.remove()

        if (cell.isNode()) {
          // 节点
          // 重置节点
          this.resetNode()

          this.currentCell = null
          this.currentCell = cell

          this.currentSelectNode = {
            attrs: {
              body: {
                fill: cell.attrs.body.fill,
                stroke: cell.attrs.body.stroke,
              },
              text: {
                fontSize: cell.attrs.text.fontSize,
                fill: cell.attrs.text.fill,
                text: cell.attrs.text.text,
              },
            },
          }
        }
      });
      // 连接线双击
      // this.graph.on("edge:dblclick", ({e, cell, view}) => {
      // 单击事件
      this.graph.on("edge:click", ({e, cell, view}) => {
        // 双击删除
        // view.cell.remove()

        if (cell.isEdge()) {
          // 节点
          // 重置节点
          this.resetEdge()

          this.currentCell = null
          this.currentCell = cell

          this.currentSelectEdge = {
            labels:[{
              attrs: {
                label:{
                  // 显示的文字
                  // text: cell.labels[0].attrs.label.text,
                  // fill: cell.labels[0].attrs.label.fill,
                  // fontSize: cell.labels[0].attrs.label.fontSize
                  text: cell.getLabelAt(0).attrs.label.text,
                  fill: cell.getLabelAt(0).attrs.label.fill,
                  fontSize: cell.getLabelAt(0).attrs.label.fontSize
                }
              },
            }],
            attrs: {
              line: {
                // 颜色
                stroke: cell.attrs.line.stroke,
                // 粗细
                strokeWidth: cell.attrs.line.strokeWidth,
                // 是否虚线
                strokeDasharray: cell.attrs.line.strokeDasharray,
              },
            }
          }
        }
      });
    },
    /**
     * 数据导出成 JSON
     * 返回一个具有 { cells: [] } 结构的对象，其中 cells 数组按渲染顺序保存节点和边
     * 节点结构：
     {
     id: string,
     shape: string,
     position: {
     x: number
     y: number
     },
     size: {
     width: number
     height: number
     },
     attrs: object,
     zIndex: number,
     }
     边
     {
     id: string,
     shape: string,
     source: object,
     target: object,
     attrs: object,
     zIndex: number,
     }

     */
    exportGraph() {
      let json = this.graph.toJSON()
      console.log(json)
    },
    /**
     * 导入
     */
    importGraph() {
      // 支持的结构
      // this.graph.fromJSON(cells: (Node.Metadata | Edge.Metadata)[])
      // 或
      this.graph.fromJSON({
        nodes: [],
        edges: [],
      })
    },
    /**
     * 放大
     */
    zoomAmplify() {
      this.graph.zoom(0.2)
    },
    /**
     * 缩小
     */
    zoomReduce() {
      this.graph.zoom(-0.2)
    },
    /**
     * 居中
     */
    zoomCenterContent() {
      this.graph.centerContent()
    },
    /**
     * 导出为图片
     */
    exportImage() {
      this.graph.exportPNG("网络拓扑图", {
        //  图片质量，可以从 0 到 1 的区间内选择图片的质量
        quality: 0.8,
        // backgroundColor: 'black'
      })
    },
    /**
     * 修改节点信息
     */
    updNodeInfo() {
      if (this.currentCell == null) {
        return
      }

      // https://x6.antv.antgroup.com/api/model/cell#%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7-attrs
      this.currentCell.setAttrs({
        body: this.currentSelectNode.attrs.body,
        text: this.currentSelectNode.attrs.text,
      })
    },
    /**
     * 更新边
     */
    updEdgeInfo() {
      if (this.currentCell == null) {
        return
      }
      this.currentCell.setAttrs({
        line: this.currentSelectEdge.attrs.line,
      })
      // this.currentCell.setLabels(this.currentSelectEdge.labels)
      // 不删除就没法更新了
      this.currentCell.removeLabelAt(0)
      this.currentCell.setLabelAt(0, this.currentSelectEdge.labels[0])
    },
    /**
     * 重置节点数据
     */
    resetNode() {
      this.currentSelectNode = {
        attrs: {
          body: {
            // 边框颜色，填充色
            fill: null,
            stroke: null,
          },
          text: {
            // 文字样式
            fontSize: null,
            fill: null,
            text: null,
          },
        },
      }
    },
    /**
     * 重置边
     */
    resetEdge() {
      this.currentSelectEdge = {
        labels:[{
          attrs: {
            label:{
              // 显示的文字
              text: null,
              fill: null,
              fontSize: 1
            }
          },
        }],
        attrs: {
          line: {
            // 颜色
            stroke: null,
            // 粗细
            strokeWidth: 1,
            // 是否虚线
            strokeDasharray: 0,
          },
        }
      }
    },
    /**
     * 根据现场id查询网络部署图
     */
    getBySiteId(){
      http.post("/opsSiteNetwork/getBySiteId", {
        siteId: this.$route.query.siteId,
      }).then(response => {
        let res = response.data;
        // 分页数据
        let data = res.data;
        this.siteNetwork = data;
        // 渲染数据
        if (this.siteNetwork != null && !checkNull(this.siteNetwork.flowChart)){
          // 1. 从 Base64 解码
          const decodedString = decodeURIComponent(escape(atob(this.siteNetwork.flowChart)));
          // 2. 解析为 JavaScript 对象
          const jsonData = JSON.parse(decodedString);
          // 重新渲染到画布
          this.graph.fromJSON(jsonData)
        }

      }).catch(error => {
        this.$message.error (error.message);
      })
    },
    /**
     * 是保存还是修改
     */
    toSaveUpd(){
      let data = this.graph.toJSON()
      if (this.siteNetwork == null){
        this.siteNetwork = {}
      }

      // 1. 将对象转为 JSON 字符串
      let jsonString = JSON.stringify(data);
      // 2. 使用 Base64 编码（浏览器原生 API）
      this.siteNetwork.flowChart = btoa(unescape(encodeURIComponent(jsonString)));
      this.siteNetwork.siteId = this.$route.query.siteId

      if (checkNull(this.siteNetwork.id)){
        this.saveOpsSiteNetwork()

      } else {
        this.updOpsSiteNetwork()
      }
    },
    /**
     * 保存
     */
    saveOpsSiteNetwork(){
      http.post("/opsSiteNetwork/saveOpsSiteNetwork", this.siteNetwork).then(response => {
        let res = response.data;

        this.$message.success ("保存成功");

      }).catch(error => {
        this.$message.error (error.message);
      })
    },
    /**
     * 更新
     */
    updOpsSiteNetwork(){
      clearInvalid(this.siteNetwork)
      http.post("/opsSiteNetwork/updOpsSiteNetwork", this.siteNetwork).then(response => {
        let res = response.data;
        this.$message.success ("更新成功");

      }).catch(error => {
        this.$message.error (error.message);
      })
    },
  }
};
</script>




<style scoped>

/*
  start --- antV X6 页面布局
*/

#container {
  display: flex;
  border: 1px solid #dfe3e8;
  height: calc(100vh - 20px);
  margin: 10px;
  box-sizing: border-box;
}

/*图形工具栏*/
#stencil {
  width: 300px;
  height: 100%;
  position: relative;
  border-right: 1px solid #dfe3e8;
}

#graph-container {
  width: calc(100% - 180px);
  height: 100%;
}

#container {
  display: flex;
  border: 1px solid #dfe3e8;
  /*height: 100vh;*/
}

/*
  end --- antV X6 页面布局
*/


</style>


<style >


.x6-widget-stencil  {
  background-color: white;
}
.x6-widget-stencil-title {
  background-color: white;
}
.x6-widget-stencil-group-title {
  background-color: white !important;
}

.x6-widget-selection-box {
  opacity: 0;
}

x6-widget-stencil-content{
  background-color: white;
}

/* 连接线特效，虚线动起来 */
@keyframes ant-line {
  to {
    stroke-dashoffset: -1000;
  }
}


</style>
